"""
  Copyright 2008 Texas A&M University Licensed under the
  Educational Community License, Version 2.0 (the "License"); you may
  not use this file except in compliance with the License. You may
  obtain a copy of the License at
  
  http://www.osedu.org/licenses/ECL-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an "AS IS"
  BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  or implied. See the License for the specific language governing
  permissions and limitations under the License.
"""
import unittest

import zope.interface
from zope.component.registry import Components

from twisted.internet.defer import Deferred, maybeDeferred

from meercat.base import Metadata
from meercat.mapreduce import interfaces, defer, exceptions, registry
from meercat.mapreduce.mappers.base import BaseMapper



class TestReducer(object):
    zope.interface.implements(interfaces.IReducer)
    
    def __init__(self, binary_function, formats):
        self._function = binary_function
        self._formats = formats
    
    def getFormats(self):
        return self._formats
    
    def reduce(self, *metadata):
        f = self._function
        
        metadata = list(metadata)
        
        while len(metadata) > 1:
            metadata.append(f(metadata.pop(),metadata.pop()))
        
        return metadata[0]

class TestMapper(BaseMapper):
    zope.interface.implements(interfaces.IMapper)
    
    def __init__(self, maps):
        """ maps argument should be a list of triples: source format, 
        destination format, and a mapping function """
        self._maps = maps
    
    def getFormats(self):
        """Return an iterable of tuples of transformations:
        [('marc21.bibliographic','marcxml.bibliographic'),
        ('marc21.holdings','marcxml.holdings')]"""
        return [(x[0],x[1]) for x in self._maps]
    
    def map(self, metadata):
        """Return the transformed metadata """
        format = metadata.getFormat()
        
        for m in self._maps:
            if m[0] == format:
                return m[2](metadata)
        
        raise exceptions.MapperError('Unknown metadata format')
        

class BaseTest(unittest.TestCase):
    def setUp(self):
        self.mapper = TestMapper([('input','output',
                                   lambda x: Metadata(source=x.getSource(),
                                                      format=x.getFormat(),
                                                      value=str(int(x.getValue()) * 2)))])
        self.reducer = TestReducer(formats=['output'],
                                   binary_function=lambda x,y: \
                                       Metadata(source='testReducer',
                                                format=x.getFormat(),
                                                value=str(int(x.getValue()) + \
                                                          int(y.getValue()))))
        
        self.registry = Components()
        self.mappers = registry.GraphMapperRegistry()
        
        self.registry.registerUtility(component=self.reducer,name='output')
        self.mappers.register(self.mapper)
    
    def testAllSuccess(self):
        metadata = [Metadata(source='test',format='input',value='1'),
                    Metadata(source='test',format='input',value='2'),
                    Metadata(source='test',format='input',value='3'),
                    Metadata(source='test',format='input',value='4')]
        
        result = defer.mapreduce(input=metadata,targetFormat='output',
                                 deferredFactory=maybeDeferred,
                                 errorBackMapErrors=True,
                                 errorBackMapPathErrors=False,
                                 maxWorkers=None,
                                 mapperRegistry=self.mappers,
                                 componentRegistry=self.registry)
        
        def testCallback(result):
            self.assertEqual(result,Metadata(source='testReducer',
                                             format='output',value='20'))
        
        result.addCallback(testCallback)
    
    def testNoPathNoError(self):
        metadata = [Metadata(source='test',format='input',value='1'),
                    Metadata(source='test',format='input',value='2'),
                    Metadata(source='test',format='input',value='3'),
                    Metadata(source='test',format='other',value='4')]
        
        result = defer.mapreduce(input=metadata,targetFormat='output',
                                 deferredFactory=maybeDeferred,
                                 errorBackMapErrors=True,
                                 errorBackMapPathErrors=False,
                                 maxWorkers=None,
                                 mapperRegistry=self.mappers,
                                 componentRegistry=self.registry)
        
        def testCallback(result):
            self.assertEqual(result,Metadata(source='testReducer',
                                             format='output',value='20'))
        
        result.addCallback(testCallback)
    
    def testNoPathError(self):
        metadata = [Metadata(source='test',format='input',value='1'),
                    Metadata(source='test',format='input',value='2'),
                    Metadata(source='test',format='input',value='3'),
                    Metadata(source='test',format='other',value='4')]
        
        result = defer.mapreduce(input=metadata,targetFormat='output',
                                 deferredFactory=maybeDeferred,
                                 errorBackMapErrors=True,
                                 errorBackMapPathErrors=True,
                                 maxWorkers=None,
                                 mapperRegistry=self.mappers,
                                 componentRegistry=self.registry)
        
        def testCallback(result):
            self.fail('No map path error not passed out')
        
        def testErrback(failure):
            self.assertTrue(failure.check(exceptions.NoPath,
                                          exceptions.UnkownFormat))
        
        result.addCallbacks(testCallback,testErrback)
    

def test_suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BaseTest))
    return suite

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(test_suite())